#!/usr/bin/perl
# map_cmp.pl
#
# April 2010, James Redus   
#
# This script is released under the terms of the MIT License:
# --------------------------------------------------------------
## Permission is hereby granted, free of charge, to any person
## obtaining a copy of this software and associated documentation
## files (the "Software"), to deal in the Software without
## restriction, including without limitation the rights to use,
## copy, modify, merge, publish, distribute, sublicense, and/or sell
## copies of the Software, and to permit persons to whom the
## Software is furnished to do so, subject to the following
## conditions:
##
## The above copyright notice and this permission notice shall be
## included in all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
## OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
## NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
## HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
## WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
## FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
## OTHER DEALINGS IN THE SOFTWARE.
# --------------------------------------------------------------
# 4/11/2010,jeredus - Added project to Google code (http://code.google.com/p/djmaputils/)
# 
#
# Perl script to read Dynojet PC3/DJM files and produce
# bitmap and html output. 
#
###########################################################
use File::Find;
use File::Basename;
use Cwd;
use Sys::Hostname;


require "getopts.pl";
require 'dumpvar.pl';
require 'pwr_cmdr.pl';
require 'pwr_cmdr_bmp.pl';

my $VERSION="Map Comparison Utility, version 0.90";


# Defaults:
my $Bn=basename($0);
my $Width=120;
my $Height=240;
my $Color_peak=25;
my $Output_dir=getcwd;
my $Name="";
my @Mapfiles=();
my @Cellvals5=();  # used in avg & stdev html tables
my @Cellvals3=();  # used in avg & stdev html tables


&Getopts('ivhd:fs:o:p:n:r:R:');
my $Usage="Experimental script to create colorized html tables and bitmaps from Power Commander 3/5 files.
Usage: perl $Bn [-v] [-h] [-i] [-f] [-n <name>] [-p <peak>] [-o <dir>] [-r <regex>] [-R <regex>] -d <dir>

where
    -v ....................... verbose/debug output
    -i ....................... interpolate the bitmap (assume gradual/fractional fuel increases)
    -f ....................... force re-write of bitmap files, even if it looks like they already exist
    -r <regex> ............... only include maps that match this regular expression (case-insensitive)
    -R <regex> ............... ignore maps that match this regular expression (case-insensitive)
    -p <peak> ................ the 'Color Peak' - cell value that gets max color (default:$Color_peak)
    -n <name> ................ optional name prefix for some output files (default: n/a)
    -d <dir> ................. directory to search for map files (.djm or .pvm)
    -s <WxH> ................. width and height of bitmap (default: ${Width}x$Height)
    -o <dir> ................. FULL PATH of output directory, which must already exist (default: $Output_dir)
    -h ....................... this Help

You have to use the -d option.

Example:

    perl $Bn -d c:/temp/some_maps/ -o /temp/my_output_dir
";

if ($opt_h) { print "$Usage\n"; exit(0); }

if (! ($opt_d) ) { 
    print "$Usage\nYou forgot to use the -d option.\n"; 
    exit(0); 
}

if ($opt_s) {
    if($opt_s =~ m/(\d+)\D+(\d+)/) {
        $Width=$1;
        $Height=$2;
    }else{
        print "$Usage\nInvalid bitmap dimensions.\n";
        exit(0);
    }
}

if ($opt_p) {
    if($opt_p =~ m/^\d+$/) {
        $Color_peak=$opt_p;
    }else{
        print "$Usage\nInvalid Color Peak ($opt_p).\n";
        exit(0);
    }
}

if ($opt_o) {
    if( -d $opt_o) {
        $Output_dir=$opt_o;
    }else{
        print "$Usage\nInvalid Output directory ($opt_o). Make sure it already exists.\n";
        exit(0);
    }
}

$Name=$opt_n if ($opt_n);

select(STDOUT); $|=1;

###########################################################
###########################################################
#
# This sub is used by the find() function. It stores anything
# that looks like a PC map file in an array.
#
sub store_map_name() {

    # print "File name is $_\n\t\tFull path is $File::Find::name\n";
    if ($File::Find::name =~ m/(djm|pvm)$/i) {
        if ($opt_r) {
            # Return unless opt_r matches
            if ( ! ($File::Find::name =~ m/$opt_r/i) ) {
                return;
            }
        }

        if ($opt_R) {
            # Return if opt_R matches
            if ( $File::Find::name =~ m/$opt_R/i ) {
                return;
            }
        }

        push (@Mapfiles,$File::Find::name);
    }
}

###########################################################
# This sub creates html tables based on a PC3 or PC5 dynojet file.
#
# Input params:
#   href = hash ref to hash returned by read_pvm_file() or read_djm_file()
#   color_peak = the fuel cell value that gets the maximum color. 
#                This should be something like 25.
#
# Return: A very long html string containing colorized fuel map tables (both pc3 & pc5).
#
sub map_2_html
{
    my ($href,$color_peak) = @_;
    my ($text,$html,%h,$tp_count,$rpm_count,@tp_array,@fuel_array,$rpm,$tp,$increment,%desc);
    my ($table1,$table2,$tp_count2,$rpm_count2,@tp_array2,@fuel_array2,$rpm,$tp,$comment1,$comment2);
    my ($B,$R,$Y,$G,$E);

    # for colorizing text:
    $G=qq|<span style="color:green">|;  
    $B=qq|<span style="color:blue">|;  
    $R=qq|<span style="color:red">|;
    $Y=qq|<span style="color:yellow">|;
    $E=qq|</span>|;

    $desc{NOTE}  = qq|<BR><table border=1 width="50%"><tr><td>|;
    $desc{NOTE} .= qq|You can highlight the fuel table contents below, and copy/paste it into the Power Commander software. NOTE: This does not work with $R Internet Explorer $E, but it does work with most other browsers ($G <strong>Firefox</strong>, Google Chrome, Apple Safari $E)<BR>|;
    $desc{NOTE} .= qq|</td></tr></table><BR>|;

    $table1="";

    %h=%{$href};  # a hash containing fuel map info
    # @fuel_array=@{$h{fuel_map}}; # OLD
    $rpm_count=$h{fuel_rpm_count};
    
    # Set some vars depending on what type of map we started with (PC3 or PC5). 
    # Also convert the map to the other type while we're at it.
    if ($h{map_type} eq "PC3") {
        @fuel_array=@{$h{pc3_fuel_map}};  # new
        $comment1="Original PC3 map";
        $comment2="PC5 map derived by interpolating 15% TP column";
        # print "PC3 Map\n"; 
        $tp_count=9;
        @tp_array=(0,2,5,10,20,40,60,80,100);
        $tp_count2=10;
        @tp_array2=(0,2,5,10,15,20,40,60,80,100);
## OLD ##        # Convert to PC5 by interpolating the TP=15 column.
## OLD ##        @fuel_array2=();
## OLD ##        for ($rpm=0; $rpm<$rpm_count; $rpm++) {
## OLD ##            for ($tp=0; $tp<$tp_count; $tp++) {
## OLD ##                push(@fuel_array2,$fuel_array[ $rpm*$tp_count + $tp ]);
## OLD ##                # Insert TP=15 column:
## OLD ##                if ($tp_array[$tp] == 10) {
## OLD ##                    # my $num=int(  ($fuel_array[$rpm*$tp_count+$tp] + $fuel_array[$rpm*$tp_count+$tp +1])/2 + .5);
## OLD ##                    my $num=($fuel_array[$rpm*$tp_count+$tp] + $fuel_array[$rpm*$tp_count+$tp +1])/2 ;
## OLD ##                    $num=int($num + .5 * ($num <=> 0));  # strange-looking avg, but it handles negatives
## OLD ##                    push(@fuel_array2,$num);
## OLD ##                }
## OLD ##            }
## OLD ##        }
        @fuel_array2=@{$h{pc5_fuel_map}};  # this calc'ed elsewhere now... 
    }elsif($h{map_type} eq "PC5" ) {
        my($trim,$trim_sum,$trim_neg,$trim_pos)=(0,0,0,0);
        # print "PC5 Map\n"; 
        $tp_count=10;
        @tp_array=(0,2,5,10,15,20,40,60,80,100);
        $tp_count2=9;
        @tp_array2=(0,2,5,10,20,40,60,80,100);
        # Apply the Autotune trim setting
## OLD ##        for ($i=0; $i<$tp_count*$rpm_count; $i++) {
## OLD ##            # Take note of how many non-zero trim cells there were.
## OLD ##            if(${$h{trim_table}}[$i]) {
## OLD ##                my ($trimval);
## OLD ##                $trim++;
## OLD ##                $trimval=${$h{trim_table}}[$i];
## OLD ##                $trim_sum+=$trimval;
## OLD ##                if($trimval > 0) {
## OLD ##                    $trim_pos++;
## OLD ##                }else{
## OLD ##                    $trim_neg++;
## OLD ##                }
## OLD ##                
## OLD ##                $fuel_array[$i] += $trimval;
## OLD ##            }
## OLD ##        }
        @fuel_array=@{$h{pc5_fuel_map_with_trim}};  # this calc'ed elsewhere now.
        if($h{trim_cell_count}) {
            $comment1="Original PC5 map with Aututune Trim applied.<BR>";
            $comment1 .= "Number of non-zero trim cells: $h{trim_cell_count}.<BR>";
            $comment1 .= "Number of pos trim cells: $h{trim_cell_pos_count}.<BR>";
            $comment1 .= "Number of neg trim cells: $h{trim_cell_neg_count}.<BR>";
            $comment1 .= "Sum of all trim cells: $h{trim_cell_sum}.<BR>";

        }else{
            $comment1="Original PC5 map (no non-zero Aututune Trim cells)";
        }
        $comment2="PC3 map derived by deleting 15% TP column";

        # Convert to PC3 by removing the 5th column (TP=15).
## OLD ##        @fuel_array2=();
## OLD ##        for ($rpm=0; $rpm<$rpm_count; $rpm++) {
## OLD ##            for ($tp=0; $tp<$tp_count; $tp++) {
## OLD ##                next if ($tp_array[$tp] == 15);
## OLD ##                push(@fuel_array2,$fuel_array[ $rpm*$tp_count + $tp ]);
## OLD ##            }
## OLD ##        }
        @fuel_array2=@{$h{pc3_fuel_map_with_trim}};  # this calc'ed elsewhere now.
    }else{
        print "Unknown hash type - aborting!\n";
        exit(1);
    }


    # A bit kudgy, but change some text if its a calc'ed map (avg, stdev)
    if ($h{map_source} eq "calculated") {
        $comment1="PC5 table";
        $comment2="PC3 table";
        $desc{NOTE}  = qq|<BR><table border=1 width="40%"><tr><td>|;
        $desc{NOTE} .= qq|The cell data from the fuel maps that was used to calculate the Average and Std Dev. tables can be seen by putting the mouse pointer over the individual cells below.<BR>|;
        $desc{NOTE} .= qq|</td></tr></table><BR>|;
    }

    # Test/debug stuff...
    # print "Map: $h{map_file}\n";
    # for ($tp=0; $tp<$tp_count; $tp++) {
    #     print "$tp_array[ $tp ] ";
    # }
    # print "\n";

    # for ($rpm=0; $rpm<$rpm_count; $rpm++) {
    #     print "${$h{fuel_rpm_labels}}[$rpm] : ";
    #     for ($tp=0; $tp<$tp_count; $tp++) {
    #         print "$fuel_array[ $rpm*$tp_count + $tp ] ";
    #     }
    #     print "\n";
    # }

    # print "\nfuel_map2:\n";
    # for ($tp=0; $tp<$tp_count2; $tp++) {
    #     print "$tp_array2[ $tp ] ";
    # }
    # print "\n";

    # for ($rpm=0; $rpm<$rpm_count; $rpm++) {
    #     print "${$h{fuel_rpm_labels}}[$rpm] : ";
    #     for ($tp=0; $tp<$tp_count2; $tp++) {
    #         print "$fuel_array2[ $rpm*$tp_count2 + $tp ] ";
    #     }
    #     print "\n";
    # }


    # Next will follow a bunch of html table gobbledygook. Basically, the fuel maps will be put into 
    # some nested html tables. 

    $table1 = qq|
      <table border="2" style="text-align: center; margin-left: auto; margin-right: auto;" cellpadding="5" cellspacing="0" bordercolor="#FF0000">
        <tr>
          <td><table width="40%"  border="1" cellpadding="5" cellspacing="0" bordercolor="#000000">
            <tr><td width="45" style="background-color: rgb(204, 204, 204);"><strong>.</strong></td></tr> |;

    # Left inner table is a column of rpm labels.
    for ($rpm=0; $rpm<$rpm_count; $rpm++) {
        my $val=${$h{fuel_rpm_labels}}[$rpm];
        $table1 .= qq|
            <tr><td style="background-color: rgb(204, 204, 204);"><strong>$val</strong></td></tr> |;
    }

    $table1 .= qq|
          </table></td> |;

    $table1 .= qq|
          <td><table style="text-align: center; margin-left: auto; margin-right: auto;"  border="1" cellpadding="5" cellspacing="0" bordercolor="#000000">
            <tr> |;

    # Right inner-table ....
    #
    # This is the column headings (i.e., throttle positions 0,2,5,...) for the data. 
    for ($tp=0; $tp<$tp_count; $tp++) {
        my $val=$tp_array[ $tp ];
        $table1 .= qq|
              <td style="background-color: rgb(204, 204, 204);" width="45"><strong>$val</strong></td> |;
    }

    $table1 .= qq|
            </tr> |;
 

    # The actual data, colorized based on $color_peak value.
    $increment=250/$color_peak;
    for ($rpm=0; $rpm<$rpm_count; $rpm++) {
        $table1 .= qq|
            <tr> |;

        for ($tp=0; $tp<$tp_count; $tp++) {
            my $val=$fuel_array[ $rpm*$tp_count + $tp ];
            my ($bgcolor,$adjust);

            if ($val > 0) {
                $adjust=255-min(int(abs($val)*$increment),250);
                $bgcolor="rgb($adjust, $adjust, 255)";  

            } elsif ($val < 0) {
                $adjust=255-min(int(abs($val)*$increment),250);
                $bgcolor="rgb(255, $adjust, $adjust)";  

            } else {
                $bgcolor="rgb(255, 255, 255)";  # white
            }
            if ($h{map_source} eq "calculated") {
                $table1 .= qq|
                  <td style="background-color: $bgcolor;" title="$Cellvals5[$rpm*$tp_count+$tp]">$val</td> |;
            }else{
                $table1 .= qq|
                  <td style="background-color: $bgcolor;">$val</td> |;
            }
        }
        $table1 .= qq|
            </tr> |;
    }

    # Finish the 1st table ...
    $table1 .= qq|
          </table></td>
        </tr>
      </table> 
    |;


    # The next table is the derived fuel map table (e.g., if original was pc3, this is pc5).
    $table2 = qq|
      <table border="2" style="text-align: center; margin-left: auto; margin-right: auto;" cellpadding="5" cellspacing="0" bordercolor="#FF0000">
        <tr>
          <td><table width="40%"  border="1" cellpadding="5" cellspacing="0" bordercolor="#000000">
            <tr><td width="45" style="background-color: rgb(204, 204, 204);"><strong>.</strong></td></tr> |;

    # Left inner table is a column of rpm labels.
    for ($rpm=0; $rpm<$rpm_count; $rpm++) {
        my $val=${$h{fuel_rpm_labels}}[$rpm];
        $table2 .= qq|
            <tr><td style="background-color: rgb(204, 204, 204);"><strong>$val</strong></td></tr> |;
    }

    $table2 .= qq|
          </table></td> |;

    $table2 .= qq|
          <td><table style="text-align: center; margin-left: auto; margin-right: auto;"  border="1" cellpadding="5" cellspacing="0" bordercolor="#000000">
            <tr> |;

    # Right inner-table ....
    #
    # This is the column headings (i.e., throttle positions 0,2,5,...) for the data. 
    for ($tp=0; $tp<$tp_count2; $tp++) {
        my $val=$tp_array2[ $tp ];
        $table2 .= qq|
              <td style="background-color: rgb(204, 204, 204);" width="45"><strong>$val</strong></td> |;
    }

    $table2 .= qq|
            </tr> |;
 

    # The actual data ...
    $increment=250/$color_peak;
    for ($rpm=0; $rpm<$rpm_count; $rpm++) {
        $table2 .= qq|
            <tr> |;

        for ($tp=0; $tp<$tp_count2; $tp++) {
            my $val=$fuel_array2[ $rpm*$tp_count2 + $tp ];
            my ($bgcolor,$adjust);

            if ($val > 0) {
                $adjust=255-min(int(abs($val)*$increment),250);
                $bgcolor="rgb($adjust, $adjust, 255)";  

            } elsif ($val < 0) {
                $adjust=255-min(int(abs($val)*$increment),250);
                $bgcolor="rgb(255, $adjust, $adjust)";  

            } else {
                $bgcolor="rgb(255, 255, 255)";  # white
            }

            if ($h{map_source} eq "calculated") {
                $table2 .= qq|
                  <td style="background-color: $bgcolor;" title="$Cellvals3[$rpm*$tp_count2+$tp]">$val</td> |;
            }else{
                $table2 .= qq|
                  <td style="background-color: $bgcolor;">$val</td> |;
            }

        }
        $table2 .= qq|
            </tr> |;
    }

    # Finish the 2nd table ...
    $table2 .= qq|
          </table></td>
        </tr>
      </table> 
    |;

    # Next, the two fuel tables are organized into an html page,
    # and the overall html page is returned as one big string. 

    # Turn newlines into <BR>s.
    $text=$h{text};
    $text =~ s/\n/<BR>/g;
    
    $html = qq|
        <title>Map detail for $h{map_basename}</title>
        
        <table width=800; border="2" style="text-align: left; margin-left: auto; margin-right: auto;" cellpadding="5" cellspacing="0" bordercolor="#000000">
          <tr>
            <td style="background-color: rgb(204, 204, 204);" width="20%"><strong>Map Filename</strong></td>  <td><strong>$h{map_file}</strong></td> 
          </tr>
          <tr>
            <td style="background-color: rgb(204, 204, 204);"><strong>Map comments</strong></td>  <td><strong>$text</strong></td> 
          </tr>
          <tr>
            <td style="background-color: rgb(204, 204, 204);"><strong>Bitmap</strong></td>  
            <td style="text-align: center"><a href="MYBITMAP"> <IMG border="2" height="200" SRC="MYBITMAP"></a></td>
          </tr>
        </table>

        <BR>
        <center>$desc{NOTE}</center><BR>

        <table border="4" style="text-align: center; margin-left: auto; margin-right: auto;" cellpadding="5" cellspacing="0" bordercolor="#000000">
          <tr>
            <td><strong>$comment1</strong></td> <td style="background-color: rgb(0, 0, 0);"></td> <td><strong>$comment2</strong></td> 
          </tr>

          <tr>
            <td>$table1</td> <td style="background-color: rgb(0, 0, 0);"></td> <td>$table2</td> 
          </tr>
        </table>
        
        <BR>
        <BR>
        |;


    return($html);


}
###########################################################
sub round
{
    my ($val)=@_;
    my ($rounded);
    $rounded = (int ($val + .5 * ($val <=> 0)));
    # print "$val ==>> $rounded\n";
    return ($rounded);
}
###########################################################
sub compute_avg_and_stdev_maps
{
    my ($href)=@_;
    my ($tp,$rpm,$map,%maphashes,%a,%s,%h,@arr,$mapcnt,$min_rpms,@rpm_labels);
    my (@avg,@sum,@sse,@stdev,@rounded_avg,@rounded_stdev);

    print "Calculating cell Averages and Standard deviations ...\n";

    %maphashes=%{$href};
    @arr=keys %maphashes;
    $mapcnt=$#arr+1;
    $min_rpms=9999;

    # find the minimum rpm_count 
    foreach $map (keys %maphashes) {
        my(%h,$rpm_count);

        %h=%{$maphashes{$map}};
        $rpm_count=$h{fuel_rpm_count};

        if ($rpm_count < $min_rpms) {
            $min_rpms = $rpm_count;
            @rpm_labels=@{$h{fuel_rpm_labels}};
        }
    }

    # print "There are $mapcnt maps\n";
    # print "Min rpm_count is $min_rpms.\n";

    # Calc the avg and stdev across all maps.
    # To keep it simple, I will use all PC5 maps (original or converted from PC3).
    # We will use the min_rpms so that we only avg cells common to all maps. 
    for ($i=0; $i<10*$min_rpms; $i++) {
        my($diff,$cnt);
        $sum[$i]=0;
        $sse[$i]=0;

        $cnt=0;
        # We'll make two passes of the data - 1st get the avg...
        foreach $map (keys %maphashes) {
            my(@fuel_array,%h);
    
            %h=%{$maphashes{$map}};
            if ($h{map_type} eq "PC3") {
                @fuel_array=@{$h{pc5_fuel_map}};
            }else{
                @fuel_array=@{$h{pc5_fuel_map_with_trim}};
            }
            $sum[$i] += $fuel_array[$i];

            # This global var is for later use:
            if ($cnt) {
                $Cellvals5[$i] .= ",$fuel_array[$i]";
            }else{
                $Cellvals5[$i] = "$fuel_array[$i]";  
            }
            $cnt++;
        }
        $avg[$i] = $sum[$i]/$mapcnt;  
        $rounded_avg[$i] = round($avg[$i]); 

        # Next do the stdev...
        foreach $map (keys %maphashes) {
            my(@fuel_array,%h);
    
            %h=%{$maphashes{$map}};
            if ($h{map_type} eq "PC3") {
                @fuel_array=@{$h{pc5_fuel_map}};
            }else{
                @fuel_array=@{$h{pc5_fuel_map_with_trim}};
            }
            $diff = $avg[$i] - $fuel_array[$i];
            $sse[$i] += $diff * $diff;  # sum of the squares of the error
        }
        $stdev[$i] = sqrt($sse[$i]/$mapcnt);
        $rounded_stdev[$i] = round($stdev[$i]);
    }

    %a=%s=(); 
    # Store some stuff that may be needed by other functions...
    $s{map_type}="PC5";
    $a{map_type}="PC5";
    $s{map_file}="calculated_stdev_map";
    $a{map_file}="calculated_avg_map";
    $s{map_basename}="calculated_stdev_map";
    $a{map_basename}="calculated_avg_map";
    $s{map_source}="calculated";
    $a{map_source}="calculated";
    $s{map_dirname}="N/A";
    $a{map_dirname}="N/A";
    $s{text}="This map contains the Standard Deviation for each cell across all $mapcnt maps.\nThese values are a measure of the cell's variation \nfrom the mean (see AVG map) across all maps.\nThe higher the number (and darker shading) the more variation for the cell.\nStatistically, about 68% of data in a population falls within 1 STDEV of\n the mean,and about 95% of the data falls within 2 STDEVs of the\n mean (assuming 'normal' distribution, etc).\nA very low STDEV value would indicate that the maps generally agree on that cell's value.";  
    $a{text}="This map contains the average value of all $mapcnt maps."; 
    $s{fuel_rpm_count}=$min_rpms;
    $a{fuel_rpm_count}=$min_rpms;
    @{$s{fuel_rpm_labels}}=@rpm_labels;
    @{$a{fuel_rpm_labels}}=@rpm_labels;
    @{$s{pc5_stdev_map}}=@stdev;
    @{$a{pc5_avg_map}}=@avg;
    @{$s{pc5_rounded_stdev_map}}=@rounded_stdev;
    @{$a{pc5_rounded_avg_map}}=@rounded_avg;
    @{$s{fuel_map}}=@rounded_stdev;
    @{$a{fuel_map}}=@rounded_avg;
    @{$s{pc5_fuel_map_with_trim}}=@rounded_stdev;
    @{$a{pc5_fuel_map_with_trim}}=@rounded_avg;

    # re-use a coupla vars to make the pc3 versions of rounded maps
    @avg=();
    @stdev=();
    for ($i=0; $i<10*$min_rpms; $i++) {
        next if ( ($i % 10) == 4);  # delete the 15% column
        push(@avg,$rounded_avg[$i]);
        push(@stdev,$rounded_stdev[$i]);
        push(@Cellvals3,$Cellvals5[$i]);  # for later use
    }

    @{$s{pc3_rounded_stdev_map}}=@stdev;
    @{$a{pc3_rounded_avg_map}}=@avg;
    @{$s{pc3_fuel_map_with_trim}}=@stdev;
    @{$a{pc3_fuel_map_with_trim}}=@avg;

    # %{$maphashes{calculated_avg_map}}=%s;
    %{$href->{calculated_avg_map}}=%a;
    %{$href->{calculated_stdev_map}}=%s;

    return;
}
###########################################################
sub check_for_dups
{
    my ($href)=@_;
    my ($map,%maphashes,$i,$j,%h,@arr,$min_rpms);
    my (%dups,%found);

    print "Checking for duplicate maps ...\n";

    %dups=%found=();
    %maphashes=%{$href};
    @arr=sort keys %maphashes;

    # Below we'll loop thru all the maps comparing them with each other and see if any match.
    # To keep it simple, I'm just checking the PC3 version of each map.
    for($i=0; $i<$#arr-1; $i++) {
        my($map1,$map2,$different);
        my($rpm_count1,$rpm_count2);
        my($tp,$rpm,%h1,%h2,@map1,@map2,$k);
        $map1=$arr[$i];
        next if ($found{$map1});
        for($j=$i+1; $j<$#arr; $j++) {
            $different=0;
            $map2=$arr[$j];
            next if ($found{$map2});  # no need to check if already found as dup
            %h1=%{$maphashes{$map1}};
            %h2=%{$maphashes{$map2}};
            $rpm_count1=$h1{fuel_rpm_count};
            $rpm_count2=$h2{fuel_rpm_count};
            if ($rpm_count1 != $rpm_count2) {
                $different=1;
                $last;
            }
            if ($h1{map_type} eq "PC3") {
                @map1=@{$h1{pc3_fuel_map}};
            }else{
                @map1=@{$h1{pc3_fuel_map_with_trim}};
            }
            if ($h2{map_type} eq "PC3") {
                @map2=@{$h2{pc3_fuel_map}};
            }else{
                @map2=@{$h2{pc3_fuel_map_with_trim}};
            }
            
            for ($k=0; $k<9*$rpm_count1; $k++) {
                if ($map1[$k] != $map2[$k]) {
                    $different=1;
                    $last;
                }
            }
            # These maps match - store in a hash
            if (! $different) {
                push(@{$dups{$map1}},$map2);
                $found{$map1}=1;
                $found{$map2}=1;
            }
        }
    }

    return(%dups);
}
###########################################################
sub get_readme
{
    my($html,$text);
    my ($B,$R,$Y,$G,$E);

    # for colorizing text:
    $G=qq|<span style="color:green">|;  
    $B=qq|<span style="color:blue">|;  
    $R=qq|<span style="color:red">|;
    $Y=qq|<span style="color:yellow">|;
    $E=qq|</span>|;
    # $body_bgcolor="AQUAMARINE";
    # $body_bgcolor="TURQUOISE";
    # $body_bgcolor="BEIGE";  
    # $body_bgcolor="ALICEBLUE";  


$html = qq|    
    <title>$VERSION Readme</title>
    <body bgcolor="BEIGE"></body>
    <big><big><strong><center>$VERSION, Readme</center></strong></big></big>
|;

$html .= qq|<BR><center><table border=1 width="50%"><tr><td>|;
$html .= qq|This is a readme for the Map Comparison Utility, which is a set of Perl scripts that can be used to read Dynojet PowerCommander III and V (.djm and .pvm) files, and produce colorized images and html pages. To run the scripts on Windows, you would first need to download and install a Perl interpreter (such as <a href="http://www.activestate.com/activeperl/downloads/">ActivePerl.</a> After installing Perl (which may ask you to reboot), you would need to go to the command prompt, and use perl to execute the map_cmp.pl script as described below. <BR><BR>$R<strong> NOTE: You need to be in the same directory as the 3 perl scripts (pwr_cmdr.pl, pwr_cmdr_bmp.pl, and map_cmp.pl when you run this utility.$E</strong> Also, as far as directory paths (directory names passed to the scripts), you should be able to use either forward slashes (/) or backslashes (\\) when running on Windows. 

|;


$html .= qq|</td></tr></table></center><BR>|;

$html .= qq|<BR><BR><big><big><strong><center>Usage:</center></strong></big></big> |;

$text = $Usage; # this is set at top of script

$text =~ s/\</&lt /g;
$text =~ s/\>/&gt/g;


$html .= qq|<BR><center><table border=0 width="30%"><tr><td>|;
$html .= qq|<pre>|;
$html .= $text; 
$html .= qq|</pre>|;
$html .= qq|</td></tr></table></center><BR>|;


$html .= qq|<BR><BR><big><big><strong><center>Parameter Details:</center></strong></big></big> |;
$html .= qq|<BR><center><table border=1 width="60%"><tr><td>|;
$html .= qq|$B Color Peak $E - This is to control the color intensity. If the peak were set to 25 then the color of fuel map values would gradually increase until the magnitude of the cell (pos. or neg.) reached 25, and then the color ($B blue $E or $R red $E) would be solid. If the color peak was 1, then any nonzero cell value would reach the peak color, and it would be hard to tell fuel variation by just glancing at the image (defeating the purpose of this utility). In general, the color peak should be somewhere near the max (min) values for a batch of fuel maps.<BR><BR> |;

$html .= qq|$B Regex $E - A 'regex' is a 'regular expression'. This is just a matching string, and it is used to filter map files. E.g., if you passed '-r pvm' to the script, then only PC5 (.pvm) maps would be read. If you used '-r djm' and '-R joe" then only PC3 (.djm) maps that do NOT contain 'joe' in the name will be read.<BR><BR> |;

$html .= qq|$B Filenames/Directories $E - This utility generates numerous files (html and bitmaps). If you specify an output directory (which must already exist), then all output files will be put there. Otherwise, they will all be put in the directory you run the script in. If you use the '-n NAME' option, then all output files will be prefixed with 'NAME_'. Once the script is finished, you should locate the index.html file (or NAME_index.html file) and load it into your web browser. <BR><BR> |;

$html .= qq|$B Interpolated Bitmaps $E - This is a bitmap (of a fuel map) in which each pixel's color is 'interpolated', based on how far from the 'center' of each fuel cell. This interpolation is based assumptions about how the power commander adjusts fuel, which may be completely false (but the bitmaps are still interesting to look at :)   <BR><BR> |;

$html .= qq|</td></tr></table></center><BR>|;


    return($html);

}
###########################################################
###########################################################
# This sub creates all of the main index pages and generates
# all html and bitmap files.
#
sub write_output_files
{
    my ($href,$width,$height,$color_peak,$output_dir,$name_prefix)=@_;
    my (%maphashes,%mapstats,%other_stats,$map,$out,%hname,%bname,%u,$html,$cnt);
    my (%desc,$label,$body_bgcolor,%dups,$readme);
    my ($B,$R,$Y,$G,$E);

    # for colorizing text:
    $G=qq|<span style="color:green">|;  
    $B=qq|<span style="color:blue">|;  
    $R=qq|<span style="color:red">|;
    $Y=qq|<span style="color:yellow">|;
    $E=qq|</span>|;


    %maphashes=%{$href};

    %dups=check_for_dups(\%maphashes);
    foreach $map (sort keys %dups) {
        push(@{$dups{$map}},$map);
        # print "DUPS: @{$dups{$map}}\n";
    }
    # $foo=scalar keys %dups;
    # print "foo=$foo\n";
    # dumpValue(\%dups);  # DEBUG
    # exit;

    compute_avg_and_stdev_maps(\%maphashes);
    %other_stats=();  # directory/file/misc stats
    %hname=%bname=%u=%desc=();
    $body_bgcolor="AQUAMARINE";
    $body_bgcolor="TURQUOISE";
    $body_bgcolor="BEIGE";  # <<-- this color isn't bad either
    $body_bgcolor="ALICEBLUE";  


    if(length($name_prefix) > 0) {
        $name_prefix .= "_";
    }

    # Go thru the map files that were found. Set the names for the bitmaps
    # and html files, and handle any same-named files found under different dirs.
    foreach $map (sort keys %maphashes) {
        my($basename,$hfile,$bfile,$bmp_sufx);

        if($opt_i) {
            $bmp_sufx="i.bmp";
        }else{
            $bmp_sufx="bmp";
        }

        # print "map=$map\n";
        $basename=basename($map);
        $u{$basename}++;   # increment a counter for like names and make part of output file names.

        $hname{$map}="$output_dir/${name_prefix}$basename.$u{$basename}.html";
        $bname{$map}="$output_dir/${name_prefix}$basename.${width}x${height}_CP${color_peak}.$u{$basename}.$bmp_sufx";
    }

    # Set html filenames and descriptive text for different html pages... 
    $hname{INDEX}="$output_dir/${name_prefix}index.html";
    $hname{README}="$output_dir/${name_prefix}readme.html";
    $hname{SORTED_BY_NAME}="$output_dir/${name_prefix}sorted_by_name.html";
    $hname{SORTED_BY_PATH}="$output_dir/${name_prefix}sorted_by_path.html";
    $hname{AVG_AND_STDEV}="$output_dir/${name_prefix}avg_and_stdev.html";
    $hname{SORTED_BY_AVG_CELL_VAL}="$output_dir/${name_prefix}sorted_by_avg_cell_val.html";
    $hname{SORTED_BY_PCT_RICH_CELLS}="$output_dir/${name_prefix}sorted_by_pct_rich_cells.html";
    $hname{SORTED_BY_WAVINESS}="$output_dir/${name_prefix}sorted_by_waviness.html";
    $hname{COMBINED}="$output_dir/${name_prefix}combined_maps.html";

    # store some comments to put in html pages...
    $desc{INDEX}="Main Map Index Page";
    $desc{SORTED_BY_NAME}="Maps sorted by Filename";
    $desc{SORTED_BY_PATH}="Maps sorted by full Directory-path + Filename";
    $desc{SORTED_BY_NET_FUEL_CHANGE}="Not in use";
    $desc{AVG_AND_STDEV}="Average and Standard Deviation 'maps'";
    $desc{SORTED_BY_AVG_CELL_VAL}="Maps sorted by average Cell Value (lean -> rich)";
    $desc{SORTED_BY_PCT_RICH_CELLS}="Maps sorted by percentage of nonzero cells that are Positive (least -> most)";
    $desc{SORTED_BY_WAVINESS}="Maps sorted by 'Waviness' Value (least -> most 'wavy')";
    $desc{COMBINED}="All HTML-table-ized maps in one file"; 

    $desc{AVG_AND_STDEV.2}  = qq|<BR><table border=1 width="50%"><tr><td>|;
    $desc{AVG_AND_STDEV.2} .= qq|This page contains the Average of all other maps, and each cell's Standard Deviation. The STDEV for a particular cell is a measure of how much variation around the cell's AVG there is amoung all of the maps. A very small STDEV would indicate that all of the maps have very similar values for those cell(s). In the STDEV map colorization, the darker $B BLUE $E it is, the higher the variation.|;
    $desc{AVG_AND_STDEV.2} .= qq|</td></tr></table><BR>|;

    $desc{SORTED_BY_AVG_CELL_VAL.2}  = qq|<BR><table border=1 width="50%"><tr><td>|;
    $desc{SORTED_BY_AVG_CELL_VAL.2} .= qq|The average cell value is the sum of all fuel cells divided by the total number of cells. This is a measure of the overall map richness. The average cell value is the middle value under the map image. If the value is positive it is colored increasingly $B BLUE $E. If the value is negative it is colored increasingly $R RED $E.|;
    $desc{SORTED_BY_AVG_CELL_VAL.2} .= qq|</td></tr></table><BR>|;

    $desc{SORTED_BY_PCT_RICH_CELLS.2}  = qq|<BR><table border=1 width="50%"><tr><td>|;
    $desc{SORTED_BY_PCT_RICH_CELLS.2} .= qq|The percentage of nonzero cells of the fuel map that are positive is a measure of 'richness area'. The percentage of 'richness area' is the leftmost value under the map image. If the percentage is gretaer than 50%, then the nonzero part of the fuel map is 'mostly rich', and the number is colored increasingly $B BLUE $E. When the percentage is less than 50%, the number is colored increasingly $R RED $E. |;
    $desc{SORTED_BY_PCT_RICH_CELLS.2} .= qq|</td></tr></table><BR>|;

    $desc{SORTED_BY_WAVINESS.2}  = qq|<BR><table border=1 width="50%"><tr><td>|;
    $desc{SORTED_BY_WAVINESS.2} .= qq|The 'waviness' value here is the sum of the number of times the fuel cells 'change directions' for all rows and columns in the fuel map. For example, the row of data 0,0,1,1,2,3,5,2,2,1 contributes 1 to the sum (the cells change directions - from increasing to decreasing - one time). This is a crude measurement, but the hope was to find a measurement for comparing the 'checkerboarded' appearance of some maps, especially autotuned pc5 maps.  The waviness value is the rightmost value under the map image, and it gets darker $Y yellow $E the more 'wavy' it is.|;
    $desc{SORTED_BY_WAVINESS.2} .= "</td></tr></table><BR>";

    $desc{COLOR_NOTE}  = qq|<BR><table border=1 width="30%"><tr><td>|;
    $desc{COLOR_NOTE} .= qq|You can go to fuel map detail pages by clicking on the images below. In the tables and images that follow, $B RICH is BLUE $E and $R LEAN is RED.$E<BR>|;
    $desc{COLOR_NOTE} .= qq|</td></tr></table><BR>|;

    # print readme..
    $readme = get_readme;
    open(README,">$hname{README}") || die "unable to write to $hname{README} ($!)";
    print README $readme;
    close(README);


    # write the bitmaps
    print "\nWriting bitmaps (size=$width X $height) ...\n";
    foreach $map (sort keys %maphashes) {
        if ( (-f $bname{$map}) && (! $opt_f) ) {
            print "$bname{$map} file exists already.\n";
        }else{
            print "Writing $bname{$map} \n";
            map_2_bmp(\%{$maphashes{$map}},$bname{$map},$width,$height,$opt_i,$color_peak,1);
            print "\n";
        }
    }

    # get some map stats
    foreach $map (keys %maphashes) {
        my(@fuel_array,%h,$rpm_count,$tpcount,$rpm,$tp);
        my($pos_count,$pos_sum,$neg_count,$neg_sum,$direction_changes);
        my($this_cell,$prev_cell,$this_direction,$last_direction);
        my ($val,$bgcolor,$adjust,$increment,$cell_count);
        
        $pos_count=$pos_sum=$neg_count=$neg_sum=$direction_changes=0;

        %h=%{$maphashes{$map}};
        $rpm_count=$h{fuel_rpm_count};
        if ($h{map_type} eq "PC3") {
            $tp_count=9;
            @fuel_array=@{$h{pc3_fuel_map}};
        }elsif($h{map_type} eq "PC5" ) {
            $tp_count=10;
            @fuel_array=@{$h{pc5_fuel_map_with_trim}};
        }else{
            die "script error (unk map_type)";
        }

        $cell_count=$rpm_count*$tp_count;  # total map cells

        for ($i=0; $i<$tp_count*$rpm_count; $i++) {
            if($fuel_array[$i] > 0) {
                $pos_count++;
                $pos_sum += $fuel_array[$i];
            }elsif($fuel_array[$i] < 0) {
                $neg_count++;
                $neg_sum += $fuel_array[$i];
            }
        }

        # AVG CELL VALUE -------------

        $mapstats{$map}{cell_count} = $cell_count;
        $mapstats{$map}{neg_cell_count} = $neg_count;
        $mapstats{$map}{pos_cell_count} = $pos_count;
        $mapstats{$map}{cell_sum} = $pos_sum + $neg_sum;
        $mapstats{$map}{cell_avg} = sprintf("%3.2f",($pos_sum+$neg_sum)/($cell_count));

        $mapstats{$map}{cell_avg_detail} = "Average cell value (sum_of_cells/count_of_cells = $mapstats{$map}{cell_sum}/$cell_count)";
        if ($mapstats{$map}{cell_avg} > 0) {
            $mapstats{$map}{cell_avg} = "+" . $mapstats{$map}{cell_avg}
        }

        # Net increase/decrease in fuel
        # Note: I switched the output to show 'cell avg value' instead of 'sum of cell values', because
        # the avg doesn't need to account for PC3 vs PC5. But the bg colorization of the output (below) is 
        # still based on the sum of cell values. 
        $increment=.20;  
        $val=$pos_sum + $neg_sum;
        $mapstats{$map}{fuel_change_net} = $val;
        # $mapstats{$map}{fuel_change_detail} = "Net Fuel increase/decrease (+$pos_sum/$neg_sum)";
        $mapstats{$map}{fuel_change_detail} = "Sum of all fuel cells (I.e., net fuel change. Pos:+$pos_sum, Neg:$neg_sum)";
        if ( $val > 0) {
            $adjust=255-min(int(abs($val)*$increment),250);
            $bgcolor="rgb($adjust, $adjust, 255)";
        }elsif($val < 0) {  # more total lean 
            $adjust=255-min(int(abs($val)*$increment),250);
            $bgcolor="rgb(255, $adjust, $adjust)"; 
        }else{
            $bgcolor="rgb(255, 255, 255)";
        }
        $mapstats{$map}{fuel_change_bgcolor} = $bgcolor;


        # % POS NONZERO CELLS  -------------
        if ( ($pos_count + $neg_count) > 0) {
            $increment=5;  # 250/50;
            $mapstats{$map}{pct_pos_cells} = sprintf("%.1f%%",$pos_count/($pos_count + $neg_count)*100);

            $val=$mapstats{$map}{pct_pos_cells};
            if($val > 50) {  # more rich cells
                $val=$val-50;
                $adjust=255-min(int(abs($val)*$increment),250);
                $bgcolor="rgb($adjust, $adjust, 255)";
            }elsif($val < 50) {  # more lean cells
                $val=50-$val;
                $adjust=255-min(int(abs($val)*$increment),250);
                $bgcolor="rgb(255, $adjust, $adjust)"; 
            }else{  # same number of rich and lean cells
                $bgcolor="rgb(255, 255, 255)";
            }
        }else{  # zero map (assign it 50%/neutral, even tho that's cheating)
            # $mapstats{$map}{pct_pos_cells} = "0.00";  
            $mapstats{$map}{pct_pos_cells} = "50.0";
            $bgcolor="rgb(255, 255, 255)";
        }
        $mapstats{$map}{pct_pos_cells_bgcolor} = $bgcolor;


        # WAVINESS  -------------
        # Try to measure "waviness" (or maybe just fuel 'direction changes')
        # My DEFN: The number of times the cells change from increasing to decreasing (or vice versa)
        # in both the horiz and vertical directions.
        # First, horiz direction ...
        for ($rpm=0; $rpm<$rpm_count; $rpm++) {
            my($cmax,$cmin)=(0,0);
            for ($tp=0; $tp<$tp_count; $tp++) {   
                if($tp==0) {
                    $last_cell=$fuel_array[ $rpm*$tp_count + $tp ];
                    $cmax=$cmin=$last_cell;

                    $last_direction=0;
                    next;
                }
                $this_cell=$fuel_array[ $rpm*$tp_count + $tp ];
                # print "$tp,$this_cell,$last_cell,[$this_direction,$last_direction]\n";
                if($this_cell == $last_cell) {
                    next;
                }elsif($this_cell > $last_cell) {  # increasing
                    $this_direction=1;
                }else{   # decreasing
                    $this_direction=-1;
                }

                $last_cell=$this_cell;
                if ( ! $last_direction ) {
                    $last_direction=$this_direction;
                    next;
                }

                # print "$tp,$this_cell,$last_cell,[$this_direction,$last_direction]\n";

                if ( $last_direction != $this_direction) {
                    # $mapstats{$map}{waviness}++; 
                    $direction_changes++;
                }

                $last_direction = $this_direction;
            }
        }
        # Next, vertical direction ...
        for ($tp=0; $tp<$tp_count; $tp++) {   
            for ($rpm=0; $rpm<$rpm_count; $rpm++) {
                if($rpm==0) {
                    $last_cell=$fuel_array[ $rpm*$tp_count + $tp ];
                    $last_direction=0;
                    next;
                }
                $this_cell=$fuel_array[ $rpm*$tp_count + $tp ];
                if($this_cell == $last_cell) {
                    next;
                }elsif($this_cell > $last_cell) {  # increasing
                    $this_direction=1;
                }else{   # decreasing
                    $this_direction=-1;
                }

                $last_cell=$this_cell;
                if ( ! $last_direction ) {
                    $last_direction=$this_direction;
                    next;
                }

                if ( $last_direction != $this_direction) {
                    # $mapstats{$map}{waviness}++; 
                    $direction_changes++;
                }

                $last_direction = $this_direction;
            }
        }
        $mapstats{$map}{waviness}=$direction_changes;
        # rgb(255, 255, 0)  # pure yellow
        # rgb(255, 255, 100)  # semi-dark yellow
        # rgb(255, 255, 200)  # light yellow
        # rgb(255, 255, 255)  # white
        $increment=1;
        $increment=1.2;
        $val=max($mapstats{$map}{waviness} - 150,0);
        $adjust=255-min(int(abs($val)*$increment),255);
        $bgcolor="rgb(255,255,$adjust)";
        $mapstats{$map}{waviness_bgcolor}="$bgcolor";

    }

    # dumpValue(\%mapstats);  # DEBUG

    open(COMBINED,">$hname{COMBINED}") || die "unable to write to $hname{COMBINED} ($!)";
    
    print "\nWriting HTML files ...\n";
    foreach $map (sort keys %maphashes) {
        my($bmpfile);
        $bmpfile=basename($bname{$map});
        $out=map_2_html(\%{$maphashes{$map}},$color_peak);
        open(HTM,">$hname{$map}") || die "unable to write to $hname{$map} ($!)";
        $out =~ s/MYBITMAP/$bmpfile/g;
        $out = qq|<body bgcolor="$body_bgcolor"></body>
           $out|;
        print "Writing $hname{$map} ...\n";
        print HTM $out;
        close(HTM);

        print COMBINED $out;
    }

    close(COMBINED);


    # Misc file/dir/other stats
    if(1) {
        my($m,%dir_cnt,%h,$bn,$dn,$at_cnt,$str,$tot,@arr,$c,$now,$host);
        %dir_cnt=();
        # %dups=();
        $host=hostname();

        $at_cnt=0;  # maps with nonzero autotune
        foreach $map (sort keys %maphashes) {
            %h=%{$maphashes{$map}};
            next if ($h{map_source} eq "calculated");
            $dn=$h{map_dirname};
            $bn=$h{map_basename};
            if ($h{map_type} eq "PC3") {
                $dir_cnt{$dn}{pc3}++;
            }else{
                $dir_cnt{$dn}{pc5}++;
            }
            $dir_cnt{$dn}{tot}++;
            $tot++;  # total maps
            if ($h{trim_cell_count}) {
                $at_cnt++;  # count of maps w/ nonzero autotune 
            }
            # $dups{$bn}++;  # count of duplicate filenames
        }
        $bn=basename($hname{README});
        $now=localtime(time);
        $str  = qq|<table border=1><tr><td>|;
        $str .= qq|<big><center>$G $VERSION $E</center></big></td></tr><tr><td>|;
        $str .= qq|See <a href="$bn">readme</a> for more information.<BR>|;
        $str .= qq|Runtime: $B $now $E on system: $B $host $E<BR>|;
        $str .= qq|Searching under $B $opt_d $E, found $B $tot $E maps : |;
        $str .= qq|<UL>|;
        foreach $dn (sort keys %dir_cnt) {
            $dir_cnt{$dn}{pc3}=0 if (! $dir_cnt{$dn}{pc3});
            $dir_cnt{$dn}{pc5}=0 if (! $dir_cnt{$dn}{pc5});
            $str .= qq|<LI>Directory $B $dn $E had $B $dir_cnt{$dn}{tot} $E maps ($dir_cnt{$dn}{pc3} PC3, $dir_cnt{$dn}{pc5} PC5)|;
        }
        $str .= qq|</UL>|;
        if (scalar keys %dups) {
            $str .= qq|Files with duplicate fuel maps (ignoring 15% TP column if it exists)|;
            $str .= qq|<UL>|;
        }
        foreach $m (sort keys %dups) {
            foreach (@{$dups{$m}}) {
                $str .= qq|<LI>$_<BR>|;
            }
            $str .= qq|<BR>|;
        }
        if (scalar keys %dups) {
            $str .= qq|</UL>|;
        }
        $str .= qq|There were $B $at_cnt $E PC5 maps with nonzero Autotune Trim cells.<BR>|;
        $str .= qq|The 'Color Peak' for this run is $B $Color_peak $E (the cell value magnitude that achieves maximum color)<BR>|;
        $str .= qq|Bitmap size is $B $Width x $Height $E pixels (width x height).<BR>|;
        $str .= qq|</td></tr></table><BR><BR>|;
        $desc{INDEX.2} = $str;
    }

    # Create the main index pages ...

    # Sort the lists of maps in different ways :
    %arr_list=();
    @arr = (sort { $mapstats{$a}{waviness} <=> $mapstats{$b}{waviness} } keys %maphashes); # by amount of waviness
    @{$arr_list{SORTED_BY_WAVINESS}}=@arr;
    # @arr = (sort { $mapstats{$a}{fuel_change_net} <=> $mapstats{$b}{fuel_change_net} } keys %maphashes); # by total richness 
    @arr = (sort { $mapstats{$a}{cell_avg} <=> $mapstats{$b}{cell_avg} } keys %maphashes); # by avg cell val 
    @{$arr_list{SORTED_BY_AVG_CELL_VAL}}=@arr;
    # @arr = (sort keys %maphashes);  # by name
    @arr = (sort { uc($maphashes{$a}{map_basename}) cmp uc($maphashes{$b}{map_basename}) } keys %maphashes); # by filename
    @{$arr_list{SORTED_BY_NAME}}=@arr;
    @arr = (sort { uc($maphashes{$a}{map_file}) cmp uc($maphashes{$b}{map_file}) } keys %maphashes); # by full pathname (map_file includes path)
    @{$arr_list{SORTED_BY_PATH}}=@arr;
    @arr = (sort { $mapstats{$a}{pct_pos_cells} <=> $mapstats{$b}{pct_pos_cells} } keys %maphashes);  # by percentage of rich cells 
    @{$arr_list{SORTED_BY_PCT_RICH_CELLS}}=@arr;

    @{$arr_list{AVG_AND_STDEV}}=(calculated_avg_map, calculated_stdev_map);

    foreach $label (SORTED_BY_NAME,SORTED_BY_PATH,SORTED_BY_AVG_CELL_VAL,SORTED_BY_PCT_RICH_CELLS,SORTED_BY_WAVINESS,AVG_AND_STDEV) {
        my($desc);
        @arr=@{$arr_list{$label}};
        $desc=$desc{$label};

        $html = qq|
            <BR><BR>
            <title>$desc</title>
            <body bgcolor="$body_bgcolor"></body>
            <big><big><strong><center>$desc</center></strong></big></big>
            <center>$desc{${label}.2}</center>
            <center>$desc{COLOR_NOTE}</center
            <BR><BR>
            <table border="2" align="center" cellpadding="5" cellspacing="0" bordercolor="#000000">|;
    
        $cnt=0;
        # foreach $map (sort keys %maphashes) {
        foreach $map (@arr) {
            my(%h,$bmpfile,$htmlfile,$mapname,$maplabel);
            %h=%{$maphashes{$map}};
            if ($label eq "AVG_AND_STDEV") {
                next unless ($h{map_source} eq "calculated");
            }else{
                next if ($h{map_source} eq "calculated");
            }
            $bmpfile=basename($bname{$map});
            $htmlfile=basename($hname{$map});
            $mapname=basename($map);
            if(length($mapname) > 20) {
                $maplabel=substr($mapname,0,20) . "...";
            }else{
                $maplabel=$mapname;
            }
            if ($h{trim_cell_count}) {
                $maplabel = "*" . $maplabel;  # Indicates that Autotune trim was applied
            }

            if(($cnt%5) == 0) {
                if($cnt > 0) {
                    $html .= qq|</TR>|;  # end previous row
                }
                $html .= qq|<TR>|;   # start a new row
            }
            $cnt++;
    
            #                <TD style="background-color: $mapstats{$map}{fuel_change_bgcolor};" title="$mapstats{$map}{fuel_change_detail}">$mapstats{$map}{fuel_change_net} </TD> 
            
            $html .= qq|
                <TD>
                    <TABLE>
                      <TR> 
                        <TD align="center"> <a href="$htmlfile"> <IMG SRC="$bmpfile" title="MAP: $map" alt="$mapname" height=250 > </a> </TD>
                      </TR>
                      <TR> 
                       <TD>
                        <TABLE border="1" style="text-align: left;" cellpadding="2" cellspacing="1" bordercolor="#000000">
                          <TR> 
                            <TD style="background-color: $mapstats{$map}{pct_pos_cells_bgcolor};" title="Percent of nonzero cells that are POSITIVE">$mapstats{$map}{pct_pos_cells} </TD> 
                            <TD style="background-color: $mapstats{$map}{fuel_change_bgcolor};" title="$mapstats{$map}{cell_avg_detail}">$mapstats{$map}{cell_avg} </TD> 
                            <TD style="background-color: $mapstats{$map}{waviness_bgcolor};" title="Waviness value"> $mapstats{$map}{waviness} </TD> </TR>
                          <TR> <TD title="$mapname" colspan="4"><small><a href="$htmlfile">$maplabel</a></small></TD> </TR>
                        </TABLE>
                       </TD>
                      </TR>
                    </TABLE>  
                </TD>|;
        }
    
        $html .= qq|</TR> </TABLE>|;  # end last row and table
    
        # open(INDEX,">$hname{INDEX}") || die "unable to write to $hname{INDEX} ($!)";
        print "Writing $hname{$label} ...\n";
        open(INDEX,">$hname{$label}") || die "unable to write to $hname{$label} ($!)";
        print INDEX $html;
        close(INDEX);
    }

    $label="INDEX";  # main index page
    $html = qq| <BR><BR> <center> <big> <title>Map Compare Main Index</title>
      <body bgcolor="$body_bgcolor"></body>
      |;

    $html .= $desc{INDEX.2};

    foreach (SORTED_BY_NAME,SORTED_BY_PATH,SORTED_BY_AVG_CELL_VAL,SORTED_BY_PCT_RICH_CELLS,SORTED_BY_WAVINESS,COMBINED,AVG_AND_STDEV) {
        my($htmlfile);

        $htmlfile=basename($hname{$_});
        $html .= qq| <a href="$htmlfile">$desc{$_}</a><BR><BR>
        |;
    }


    $html .= qq|</big></center>|;


    print "Writing $hname{$label} ...\n";
    open(INDEX,">$hname{$label}") || die "unable to write to $hname{$label} ($!)";
    print INDEX $html;
    close(INDEX);

    # print "PC version=$PC_version\n";


}
###########################################################

$time0=time;
%maphashes=();
if ($opt_d) {
    if (! -d $opt_d ) {
        print "Invalid directory ($opt_d). $Usage\n";
        exit(1);
    }
    print "Searching directory $opt_d for map files - this could take a few minutes.\n";
    find(\&store_map_name, "$opt_d");

} else {
    print "You forgot to use the -d option.\n$Usage\n"; 
    exit(0); 
}

# print "Bitmaps will be $Width x $Height.\n";
$cnt=$#Mapfiles+1;
if ($cnt == 0) {
    print "No matching files found - exiting\n";
    exit;
}
print "Reading $cnt Dynojet map files.";
foreach $mapfile (@Mapfiles) {
    my $bmp_name;
    print ".";
    if ($mapfile =~ m/djm/i) {
        %{$maphashes{$mapfile}}=read_djm_file($mapfile,$opt_v);
    }elsif($mapfile =~ m/pvm/i) {
        %{$maphashes{$mapfile}}=read_pvm_file($mapfile,$opt_v);
    }else{
        next;
    }
    
##    if($opt_i) {
##        $bmp_name=$mapfile . ".i.bmp";
##    }else{
##        $bmp_name=$mapfile . ".bmp";
##    }
##
##    print "Writing $bmp_name ";
##    map_2_bmpi(\%h,"$bmp_name",$Width,$Height,$opt_i,$Color_peak); 

}

print "\n";

write_output_files(\%maphashes,$Width,$Height,$Color_peak,$Output_dir,$Name);

# dumpValue(\%maphashes);  # DEBUG

$time2=time;


printf("Total running time = %d seconds (%.1f minutes)\n",($time2-$time0),($time2-$time0)/60.0);
exit;



